package org.jflame.web;

import org.jflame.commons.exception.BusinessException;
import org.jflame.commons.file.FileHelper;
import org.jflame.commons.file.MimeTypes;
import org.jflame.commons.file.MyFileTypeDetector;
import org.jflame.commons.model.CallResult;
import org.jflame.commons.util.ArrayHelper;
import org.jflame.commons.util.IOHelper;
import org.jflame.commons.util.StringHelper;
import org.jflame.context.filemanager.FileAccessException;
import org.jflame.context.filemanager.FileManagerFactory;
import org.jflame.context.filemanager.IFileManager;
import org.jflame.context.filemanager.OssFileManager;
import org.jflame.context.filemanager.PresignedUrlArgs;
import org.jflame.context.filemanager.SignedUrl;
import org.jflame.web.WebFileProperties.DownloadProp;
import org.jflame.web.WebFileProperties.UploadProp;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;

/**
 * 文件上传下载服务类
 *
 * @author charles.zhang
 */
public class WebFileService {

    protected final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * 下载文件,只适合小文件下载使用
     *
     * @param downFile 要下载的文件相对路径
     * @param prop 属性
     * @param response HttpServletResponse
     * @throws IOException
     */
    public void download(String downFile, DownloadProp prop, HttpServletResponse response) throws IOException {
        ServletOutputStream output = null;
        InputStream input = null;
        String ext = FileHelper.getExtension(downFile, false);
        if (prop.isAllowDownload(ext)) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "This type of file is not allowed to download");
            return;
        }
        try {
            response.reset();
            // 文档打开而不是弹出下载框,写入文件类型对应的ContentType到httpheader,默认写入Content-Disposition:attachment作为下载流
            if (prop.isKeepContentType()) {
                response.setContentType(MimeTypes.getInstance()
                        .getMimetypeByExt(ext));
            } else {
                WebUtils.setFileDownloadHeader(response, FileHelper.getFilename(downFile), null);
            }
            input = FileManagerFactory.getCurrentManager()
                    .readStream(prop.downloadFullPath(downFile));
            output = response.getOutputStream();
            // 使用缓冲流输出
            IOHelper.copy(input, output);
            output.flush();
        } catch (FileAccessException e) {
            log.error("下载文件异常:" + downFile, e);
            if (FileAccessException.FILE_NOT_FOUND.equals(e.getErrcode())) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
            } else {
                response.sendError(HttpServletResponse.SC_BAD_GATEWAY);
            }
        } catch (IOException e) {
            log.error("下载文件异常:" + downFile, e);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } finally {
            IOHelper.closeQuietly(input);
            IOHelper.closeQuietly(output);
        }
    }

    /**
     * 上传文件
     *
     * @param prop 上传文件属性
     * @param request HttpServletRequest
     * @return 上传文件和上传后路径map
     * @throws IOException
     * @throws ServletException
     */
    public Map<String,String> upload(UploadProp prop, HttpServletRequest request) throws IOException, ServletException {
        Collection<Part> parts = request.getParts();
        if (prop == null) {
            prop = new UploadProp();
        }
        // 单独指定了检查文件大小, 默认使用容器的配置参数
        if (prop.getMaxFileSize() > 0) {
            for (Part part : parts) {
                if (prop.isSizeExceeds(part.getSize())) {
                    throw new BusinessException("文件大小超过限制,最大" + prop.maxFileSizeAsKB() + "KB", 4001);
                }
            }
        }
        Map<String,String> resultMap = new HashMap<>();
        String tmpUrl;
        IFileManager fileManager = FileManagerFactory.getCurrentManager();
        for (Part part : parts) {
            tmpUrl = save(part.getInputStream(), part.getSubmittedFileName(), prop);
            resultMap.put(part.getSubmittedFileName(),
                    prop.isReturnAbsoluteUrl() ? fileManager.toRequestUrl(tmpUrl) : tmpUrl);
        }
        return resultMap;
    }

    /**
     * 从输入流只在上传文件
     *
     * @param inputStream 文件输入流
     * @param originalFileName 上传文件名
     * @param prop 上传文件属性
     * @return 返回文件上传后的相对路径
     * @throws FileAccessException 上传文件保存异常时抛出
     * @throws BusinessException 上传文件条件检查失败时抛出
     */
    protected String save(InputStream inputStream, String originalFileName, UploadProp prop)
            throws FileAccessException {
        String ext = FileHelper.getExtension(originalFileName, false);
        String savePath = prop.uploadFullPath(originalFileName);
        String newFile;
        try {
            if (prop.isStrictlyTypeDetect() || StringHelper.isEmpty(ext)) {
                BufferedInputStream inputBufStream = new BufferedInputStream(inputStream);
                String[] possibleTypes = MyFileTypeDetector.detect(inputStream);
                if (ArrayHelper.isEmpty(possibleTypes)) {
                    throw new BusinessException("无法识别的文件类型", 4002);
                }
                // 检测到1个可能的类型
                if (possibleTypes.length == 1) {
                    if (!prop.isAllowUpload(possibleTypes[0])) {
                        log.error("不允许上传的文件类型,文件名:{},检测到的可能类型:{}", originalFileName, possibleTypes[0]);
                        throw new BusinessException("不允许上传的文件类型", 4003);
                    }
                } else {
                    // 检测到多个可能的类型,如果有扩展名先判断下扩展名是否在可能类型里
                    if (StringHelper.isEmpty(ext) && !ArrayUtils.contains(possibleTypes, ext)) {
                        log.error("检测的实际文件类型与扩展名不符,文件名:{},检测到的可能类型:{}", originalFileName,
                                Arrays.toString(possibleTypes));
                        throw new BusinessException("文件实际类型与扩展名不符合", 4004);
                    }

                    if (!prop.isAllowUpload(ext)) {
                        throw new BusinessException("不允许上传的文件类型", 4003);
                    }
                }

                newFile = FileManagerFactory.getCurrentManager()
                        .save(inputBufStream, savePath, prop.getFileMeta());
            } else {
                newFile = FileManagerFactory.getCurrentManager()
                        .save(inputStream, savePath, prop.getFileMeta());
            }

        } finally {
            IOHelper.closeQuietly(inputStream);
        }
        return newFile;
    }

    /**
     * 获取终端post方式直传参数
     *
     * @param fileExt 文件扩展名
     * @param prop 上传属性
     * @return 直传参数
     */
    @SuppressWarnings({ "unchecked","rawtypes" })
    public CallResult<Map<String,String>> generatePostObjectParams(String fileExt, UploadProp prop) {
        CallResult<Map<String,String>> result = CallResult.ok();
        if (StringHelper.isEmpty(fileExt)) {
            return result.paramerror("请输入要上传的文件扩展名");
        }
        if (!prop.isAllowUpload(fileExt)) {
            result.status(4003)
                    .message("不允许上传的文件类型");
        }
        IFileManager fileManager = FileManagerFactory.getCurrentManager();
        if (fileManager instanceof OssFileManager) {
            PresignedUrlArgs urlArgs = prop.toPresignedUrlArgs(fileExt);
            try {
                Map<String,String> respMap = ((OssFileManager) fileManager).generatePostObjectParams(urlArgs);
                return result.success(respMap);
            } catch (RuntimeException e) {
                log.error("生成oss post直传参数异常", e);
                return result.error("生成POST直传参数失败");
            }

        } else {
            return result.error("当前文件管理方式不支持该接口");
        }
    }

    /**
     * 获取第三方直传签名rul
     *
     * @param fileExt 文件名扩展名
     * @param prop 上传属性
     */
    @SuppressWarnings("rawtypes")
    public CallResult<SignedUrl> generatePresignPutUrl(String fileExt, UploadProp prop) {
        IFileManager fileManager = FileManagerFactory.getCurrentManager();
        CallResult<SignedUrl> result = CallResult.ok();
        if (StringHelper.isEmpty(fileExt)) {
            return result.paramerror("请输入要上传的文件扩展名");
        }
        if (!prop.isAllowUpload(fileExt)) {
            result.status(4003)
                    .message("不允许上传的文件类型");
        }
        if (fileManager instanceof OssFileManager) {
            PresignedUrlArgs urlArgs = prop.toPresignedUrlArgs(fileExt);
            try {
                SignedUrl respMap = ((OssFileManager) fileManager).generatePresignedPutUrl(urlArgs);
                return result.success(respMap);
            } catch (RuntimeException e) {
                log.error("获取直传预签名url异常", e);
                return result.error("生成上传预签名地址失败");
            }
        } else {
            return result.error("当前文件管理方式不支持该接口");
        }
    }

}
